/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.dialog.schema.model;

import offset.nodes.client.virtual.model.GetSchemas;
import offset.nodes.client.model.AbstractSchemaTree;
import offset.nodes.client.model.HierarchyTree;
import offset.nodes.client.virtual.model.SchemaNode;
import offset.nodes.client.virtual.model.SchemaProperty;
import offset.nodes.client.model.NodeTypes;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ResourceBundle;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JMenu;
import javax.swing.JMenuBar;
import javax.swing.JPanel;
import javax.swing.JToolBar;
import javax.swing.JTree;
import javax.swing.text.Document;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreePath;
import offset.nodes.Constants;
import offset.nodes.client.dialog.schema.view.NodeTypeDefinitionsWriter;
import offset.nodes.client.dialog.schema.view.SchemaNodePanel;
import offset.nodes.client.dialog.schema.view.SchemaPropertyPanel;
import offset.nodes.client.virtual.model.SchemaItem;
import offset.nodes.client.model.Server;
import offset.nodes.client.tree.model.DetailPanelTreeConfiguration;
import offset.nodes.client.tree.model.DetailPanelTreeNodeType;
import offset.nodes.client.model.TreeNodeUserObject;
import offset.nodes.client.virtual.model.jcr.QName;
import offset.nodes.client.virtual.model.jcr.nodetype.NodeTypeDefinition;
import offset.nodes.client.virtual.model.jcr.nodetype.NodeTypeReader;
import offset.nodes.client.tree.view.DetailPanelTreeConfiguratorPanel;

/**
 *
 * @author Walter Lütgenau
 */
public class SchemaConfiguration implements DetailPanelTreeConfiguration {

    public static final int LAYOUT_FLAT = 1;
    public static final int LAYOUT_HIERARCHY = 2;
    NodeTypes types;
    JTree tree = null;
    AbstractSchemaTree treeRepresentation;
    DetailPanelTreeNodeType[] nodeTypes = {
        new RootType(), new NodeType(), new PropertyType()
    };
    Server server;

    /** Creates a new instance of SchemaTreeModel
     * @param server the model
     */
    public SchemaConfiguration(Server server) {
        this.server = server;
    }

    public Object getResult() {
        return types.getModifications();
    }

    public void removeNode(DefaultMutableTreeNode remove) {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) ((DefaultTreeModel) tree.getModel()).getRoot();
        if (remove == root) {
            return;
        }
        DefaultMutableTreeNode parent = (DefaultMutableTreeNode) remove.getParent();

        if (parent == root) {
            treeRepresentation.removeType(remove);
            types.removeType(((SchemaNode) remove.getUserObject()).getNodeType().getName());
        } else {
            treeRepresentation.removeChildFromType(parent, remove);
            if (remove.getUserObject() instanceof SchemaNode) {
                types.removeChildNode(((SchemaNode) parent.getUserObject()).getNodeType().getName(), ((SchemaNode) remove.getUserObject()).getNode());
            } else if (remove.getUserObject() instanceof SchemaProperty) {
                types.removeProperty(((SchemaNode) parent.getUserObject()).getNodeType().getName(), ((SchemaProperty) remove.getUserObject()).getProperty().getName());
            }
        }

        TreePath parentPath = new TreePath(parent.getPath());
        tree.setSelectionPath(parentPath);
    }

    public void cancelNodeModification(DetailPanelTreeNodeType type) {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getSelectionModel().getLeadSelectionPath().getLastPathComponent();
        TreeNodeUserObject userObject = (TreeNodeUserObject) node.getUserObject();

        if (type.getName().equals(SchemaItem.TYPE_NODE)) {
            ((SchemaNodePanel) type.getDetailPanel()).setPanel(userObject);
        } else {
            ((SchemaPropertyPanel) type.getDetailPanel()).setPanel(userObject);
        }
    }

    public void acceptNodeModification(DetailPanelTreeNodeType type) {
        boolean isChild = false;
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) tree.getSelectionModel().getLeadSelectionPath().getLastPathComponent();
        DefaultMutableTreeNode parent = (DefaultMutableTreeNode) node.getParent();

        if (type.getName().equals(SchemaItem.TYPE_NODE)) {
            SchemaNodePanel panel = (SchemaNodePanel) type.getDetailPanel();
            if (panel.getNameTextField().isEnabled()) {
                isChild = true;
            }
            SchemaNode def = (SchemaNode) panel.getUserObject();
            if (isChild) {
                types.setChildNode(((SchemaNode) parent.getUserObject()).getNodeType().getName(), def.getNode());
                if (!types.contains(def.getNodeType().getName())) {
                    types.setType(def.getNodeType());
                }
            } else {
                types.removeType(((SchemaNode) node.getUserObject()).getNodeType().getName());
                types.setType(def.getNodeType());
            }
            node.setUserObject(def);
        } else {
            SchemaPropertyPanel panel = (SchemaPropertyPanel) type.getDetailPanel();
            if (panel.getNameTextField().isEnabled()) {
                isChild = true;
            }
            SchemaProperty property = (SchemaProperty) panel.getUserObject();
            types.setProperty(((SchemaNode) parent.getUserObject()).getNodeType().getName(), property.getProperty());
            node.setUserObject(property);
        }

        ((DefaultTreeModel) tree.getModel()).nodeChanged(node);
    // TODO: change other tree (hierarchy/flat) as well
    }

    protected DetailPanelTreeNodeType getNodeType(String name) {
        for (int i = 0; i < nodeTypes.length; i++) {
            if (nodeTypes[i].getName().equals(name)) {
                return nodeTypes[i];
            }
        }
        return null;
    }

    /**
     * Insert a node into the object tree.
     * 
     * @param parent The parent node
     * @param position The position of the new child
     * @param typeName The name of the type of the new child
     */
    public void insertNodeAt(DefaultMutableTreeNode parent, int position, String typeName) {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) ((DefaultTreeModel) tree.getModel()).getRoot();
        DefaultMutableTreeNode insert = new DefaultMutableTreeNode();

        DetailPanelTreeNodeType nodeType = getNodeType(typeName);
        if (nodeType == null) {
            return;
        }

        TreeNodeUserObject userObject = null;
        if (parent == root) {
            if (typeName.equals(SchemaItem.TYPE_NODE)) {
                userObject = SchemaNode.newType();
            }
        } else {
            userObject = nodeType.createUserObject();
        }

        insert.setUserObject(userObject);

        if (parent == root) {
            treeRepresentation.addType(insert, position);
        } else {
            treeRepresentation.addChildToType((DefaultMutableTreeNode) parent, insert, position);
        }

        TreePath childPath = new TreePath(insert.getPath());
        tree.setSelectionPath(childPath);
    }

    public void writeTextualRepresentation(Document document, ResourceBundle bundle) {
        NodeTypeDefinitionsWriter writer = new NodeTypeDefinitionsWriter(types.getModifications(),
                NodeTypeDefinitionsWriter.MODIFICATIONS_NEW |
                NodeTypeDefinitionsWriter.MODIFICATIONS_MODIFIED |
                NodeTypeDefinitionsWriter.MODIFICATIONS_REMOVED);
        try {
            document.remove(0, document.getLength());
            writer.write(document, bundle);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public DetailPanelTreeNodeType[] getNodeTypes() {
        return nodeTypes;
    }

    public DetailPanelTreeConfiguration getModel() {
        return this;
    }

    public void init(JTree tree, JToolBar toolBar, JMenuBar menu) {
        // initialize model and populate trees
        this.tree = tree;

        types = new NodeTypes(initTypes());
        
        setupTree(filter, LAYOUT_HIERARCHY);

        extendMenu(menu);
    }
    private javax.swing.JRadioButtonMenuItem dataOnlyRadio = new javax.swing.JRadioButtonMenuItem();
    private javax.swing.JRadioButtonMenuItem allRadio = new javax.swing.JRadioButtonMenuItem();
    private javax.swing.JRadioButtonMenuItem hierarchyRadio = new javax.swing.JRadioButtonMenuItem();
    private javax.swing.JRadioButtonMenuItem flatRadio = new javax.swing.JRadioButtonMenuItem();
    private javax.swing.ButtonGroup viewStructureButtonGroup = new javax.swing.ButtonGroup();
    private javax.swing.ButtonGroup viewFilterButtonGroup = new javax.swing.ButtonGroup();
    private javax.swing.JSeparator separator1 = new javax.swing.JSeparator();
    private javax.swing.JSeparator separator2 = new javax.swing.JSeparator();
    int layout = LAYOUT_HIERARCHY;
    QName[] nodesFilter = new QName[] {new QName(Constants.URI_NODES_DATA, ""), new QName(Constants.URI_NODES, "")};
    QName[] filter = nodesFilter;

    protected void extendMenu(JMenuBar menuBar) {
        JMenu viewMenu = menuBar.getMenu(DetailPanelTreeConfiguratorPanel.MENU_VIEW);
        if (viewMenu == null) {
            return;
        }

        viewMenu.add(separator1);

        viewFilterButtonGroup.add(dataOnlyRadio);
        dataOnlyRadio.setSelected(true);
        dataOnlyRadio.setText(java.util.ResourceBundle.getBundle("offset/nodes/client/dialog/view/DialogBundle").getString("schema.viewMenu.showDataOnly"));
        dataOnlyRadio.addActionListener(new java.awt.event.ActionListener() {

            public void actionPerformed(java.awt.event.ActionEvent evt) {
                showNodesTypesOnly(evt);
            }
        });

        viewMenu.add(dataOnlyRadio);

        viewFilterButtonGroup.add(allRadio);
        allRadio.setText(java.util.ResourceBundle.getBundle("offset/nodes/client/dialog/view/DialogBundle").getString("schema.viewMenu.showAll"));
        allRadio.addActionListener(new java.awt.event.ActionListener() {

            public void actionPerformed(java.awt.event.ActionEvent evt) {
                showAll(evt);
            }
        });

        viewMenu.add(allRadio);

        viewMenu.add(separator2);

        viewStructureButtonGroup.add(hierarchyRadio);
        hierarchyRadio.setSelected(true);
        hierarchyRadio.setText(java.util.ResourceBundle.getBundle("offset/nodes/client/dialog/view/DialogBundle").getString("view.hierarchy"));
        hierarchyRadio.addActionListener(new java.awt.event.ActionListener() {

            public void actionPerformed(java.awt.event.ActionEvent evt) {
                showHierarchy(evt);
            }
        });

        viewMenu.add(hierarchyRadio);

        viewStructureButtonGroup.add(flatRadio);
        flatRadio.setText(java.util.ResourceBundle.getBundle("offset/nodes/client/dialog/view/DialogBundle").getString("view.flat"));
        flatRadio.addActionListener(new java.awt.event.ActionListener() {

            public void actionPerformed(java.awt.event.ActionEvent evt) {
                showFlat(evt);
            }
        });

        viewMenu.add(flatRadio);

    }

    private void showFlat(java.awt.event.ActionEvent evt) {
        setupTree(filter, layout = LAYOUT_FLAT);
    }

    private void showHierarchy(java.awt.event.ActionEvent evt) {
        setupTree(filter, layout = LAYOUT_HIERARCHY);
    }

    private void showNodesTypesOnly(java.awt.event.ActionEvent evt) {
        setupTree(filter = nodesFilter, layout);
    }

    private void showAll(java.awt.event.ActionEvent evt) {
        setupTree(filter = null, layout);
    }
    
    class RootType implements DetailPanelTreeNodeType {

        public String getName() {
            return "";
        }

        public JPanel getDetailPanel() {
            return null;
        }

        public Icon getIcon() {
            return null;
        }

        public TreeNodeUserObject createUserObject() {
            return null;
        }        
   
        public boolean canAccept(String typeName) {
            return true;
        }
 }

    class NodeType implements DetailPanelTreeNodeType {

        ImageIcon nodeIcon = new ImageIcon(SchemaConfiguration.class.getResource("/offset/nodes/client/dialog/view/resources/sc_data.png"));
        SchemaNodePanel panel = new SchemaNodePanel();

        public String getName() {
            return SchemaItem.TYPE_NODE;
        }

        public ImageIcon getIcon() {
            return nodeIcon;
        }

        public TreeNodeUserObject createUserObject() {
            return SchemaNode.newNode();
        }

        public JPanel getDetailPanel() {
            panel.setTypes(types);
            return panel;
        }
    
        public boolean canAccept(String typeName) {
            return true;
        }
}

    class PropertyType implements DetailPanelTreeNodeType {

        ImageIcon propertyIcon = new ImageIcon(SchemaConfiguration.class.getResource("/offset/nodes/client/dialog/view/resources/sc_property.png"));
        SchemaPropertyPanel panel = new SchemaPropertyPanel();

        public String getName() {
            return SchemaItem.TYPE_PROPERTY;
        }

        public ImageIcon getIcon() {
            return propertyIcon;
        }

        public TreeNodeUserObject createUserObject() {
            return SchemaProperty.newProperty();
        }

        public JPanel getDetailPanel() {
            panel.setTypes(types);
            return panel;
        }
   
        public boolean canAccept(String typeName) {
            return true;
        }
 }

    protected void setupTree(QName[] filter, int layoutType) {
        NodeTypeDefinition[] definitions = null;
        if ( filter != null ) 
            definitions = types.getArray(filter);
        else
            definitions = types.getArray();

        if (layoutType == LAYOUT_HIERARCHY) {
            ((DefaultTreeModel) tree.getModel()).setRoot(HierarchyTree.buildTree(types, definitions));
            treeRepresentation = new HierarchyTree(tree);
        } else {
            ((DefaultTreeModel) tree.getModel()).setRoot(FlatTree.buildTree(types, definitions));
            treeRepresentation = new FlatTree(tree);
        }

    }

    public NodeTypeDefinition[] initTypes() {
        GetSchemas.Request request = new GetSchemas.Request();
        GetSchemas.Response response = null;
        try {
            response = (GetSchemas.Response) server.sendRequest(request);
            InputStream in = new ByteArrayInputStream(response.getSchemaXml().getBytes());
            NodeTypeDefinition[] definitions = NodeTypeReader.read(in);

            return definitions;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
}
